package org.tigr.htc.persist;

/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
import org.exolab.castor.jdo.OQLQuery;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.jdo.QueryResults;
import org.exolab.castor.persist.spi.Complex;
import org.tigr.htc.cmd.Command;
import org.tigr.htc.cmd.ICommand;
import org.tigr.htc.common.HTCConfig;

/**
 * The class <b>CastorJDO</b> is the implementation of the persistence layer using JDO.
 * The class provides the methods to store and retrieve the information from the database. 
 */
public class CastorJDO implements IPersist {
    
    /** The field <code>log</code> */
    static Logger logger = Logger.getLogger("org.tigr.htc.persist.CastorJDO");
    static String databaseConf = HTCConfig.getProperty("CommandDatabase");
    
    /** The field <code>jdo</code> */
    private static JDOManager jdo;
    
    /**
     * The constructor <code>CastorJDO</code> creates an instance of the class
     *
     */
    public CastorJDO() {
        
        try {
            // load the JDOManager configuration file
            JDOManager.loadConfiguration(databaseConf);
            
            // construct a new JDOManager for 'MySQL'
            jdo = JDOManager.createInstance("MySQL");
        }
        catch (Exception e) {
            logger.fatal("error configuring JDO: " + e.getMessage(), e);
        }
        logger.info(" database configuration finished ");
    }
    
    /**
     * The method <code>getNewID<code> returns the new ID that can be used
     *
     * @return a <code>long</code> value representing the next command ID
     *
     */
    public synchronized long  getNewID() {
        
        if (logger.isDebugEnabled()) {
            logger.debug("in getNewID ");
        }
        
        Database db = null;
        CUID cuid = new CUID();
        long id = 0;
        
        try {
            db = jdo.getDatabase();
            
            if (logger.isDebugEnabled()) {
                logger.debug(" create in ID");
            }
            db.begin();
            db.create(cuid);
            db.commit();
        }
        catch(Exception e) {
            logger.fatal("persistCommand error: " + e.getMessage(), e);
            return 0;
        } finally {
            try {
                if (logger.isDebugEnabled()) {
                    logger.debug(" cuid is " + cuid.getID());
                }
                close(db);
                if (logger.isDebugEnabled()) {
                    logger.debug("End create ID transaction " + id);
                }
            } catch(Exception e) {
                logger.fatal("Close database  error: " + e.getMessage(), e);
                return 0;
            }
        }
        
        id = cuid.getID();
        
        return id;
    }
    
    // 
    // Start Command methods
    // 
    
    /**
     * The method <code>persistCommand<code>
     *
     *
     * @param p_cmd
     *
     */
    public synchronized void create(Object p_obj) {
        
        logger.debug("Persist Object " + p_obj.getClass());
        
        Database db = null;
        
        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Start save command transaction");
            }
            db = jdo.getDatabase();
            db.begin();
            db.create(p_obj);
            db.commit();
            
        }
        catch (Exception e) {
            logger.fatal("Persist object error: " + e.getMessage(), e);
        } finally {
            try {
                close(db);
                if (logger.isDebugEnabled()) {
                    logger.debug("End save command transaction");
                }
            } catch(Exception e) {
                logger.fatal("Close database error: " + e.getMessage());
            }
        }
    }
    
    
    /**
     * The method <code>updateCommand<code>
     *
     * @param p_cmd
     *
     */
    public synchronized void update(Object p_obj){
        
        logger.debug("Update Object of type " +p_obj.getClass().getName());
        
        Database db = null;
        
        // Now, update the Command. In case of ClassCastException, retry.
        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Begin updating command trans");
            }
        	
            db = jdo.getDatabase();
            db.begin();
            db.update(p_obj);
            db.commit();
        } catch (PersistenceException pe) {
        	logger.warn("updateCommand Persistence Exception :" + pe.getMessage()); 
		}
        finally {
            try {
                close(db);
                if (logger.isDebugEnabled()) {
                    logger.debug("end updating command trans");
                }
            } catch(Exception e) {
                logger.fatal("close database  error: " + e.getMessage());
            } 
        }
    }
    
    /**
     * The method <code>findCommand<code>
     *
     *
     * @param p_id
     *
     * @return
     *
     */
    public synchronized Object find(Complex p_id, Class p_class) {
        
        if (logger.isDebugEnabled()) {
            logger.debug(" find object " + p_id);
        }
        
        Database db = null;
        Object obj = null;
        
        try {
            db = jdo.getDatabase();
            
            db.begin();
            
            obj = db.load(p_class, p_id);
            
            db.commit();
        }
        catch (Exception e) {
            logger.fatal("find error: " + e.getMessage());
            return obj;
        } finally {
            try {
                close(db);
                if (logger.isDebugEnabled()) {
                    logger.debug("end findCommand command trans");
                }
            } catch(Exception e) {
                logger.fatal("close database  error: " + e.getMessage());
            }
        }
        
        return obj;
    }
    
    
    /**
     * The method <code>findCommand<code>
     * 
     * @param p_id
     *
     * @return
     *
     */
    public synchronized Object find(long p_id, Class pClass) {
        if (logger.isDebugEnabled()) {
            logger.debug("Find object " + p_id);
        }
        
        Database db = null;
        Object obj = null;
        
        try {
            db = jdo.getDatabase();
            
            db.begin();
            
            obj = db.load(pClass, new Long(p_id));
            
            db.commit();
        }
        catch (Exception e) {
            logger.fatal("find error: " + e.getMessage());
            return obj;
        } finally {
            try {
                close(db);
                if (logger.isDebugEnabled()) {
                    logger.debug("End findCommand Command trans");
                }
            } catch (Exception e) {
                logger.fatal("Close database error: " + e.getMessage());
            }
        }
        
        return obj;
    }
    
    /**
     * The method <code>searchCommands<code>
     *
     * @param p_where
     * @param p_order
     * @param p_limit
     *
     * @return
     *
     */
    public synchronized List searchCommands(String p_where, String p_order, String p_limit) {
        
        if (logger.isDebugEnabled()) {
            logger.debug(" searchCommands ");
        }
        
        Database db = null;
        OQLQuery oql;
        QueryResults oqlResults;
        Object cmd = null;
        ArrayList results = new ArrayList();
        StringBuffer query = new StringBuffer("SELECT c FROM org.tigr.htc.cmd.Command c");
        
        if((p_where != null) && ( !p_where.equals(""))) {
            query.append(" WHERE " + p_where);
        }
        
        if((p_order != null) && !p_order.equals("")) {
            query.append(" ORDER BY " + p_order);
        }
        
        if((p_limit != null) && !p_limit.equals("")) {
            query.append(" LIMIT " + p_limit);
        }
        
        try {
            db = jdo.getDatabase();
            // Expire the cache to make sure that all the old command states
            // are refreshed
            // CacheManager manager = ((DatabaseImpl) db).getCacheManager();
            // manager.expireCache();
            
            if (logger.isDebugEnabled()) {
                logger.debug("searching commands " + query);
            }
            
            db.begin();
            
            oql = db.getOQLQuery(query.toString());
            
            oqlResults = oql.execute(Database.ReadOnly);
            
            if (logger.isDebugEnabled()) {
                logger.debug("found " + oqlResults.size());
            }
            
            while(oqlResults.hasMore()) {
                cmd = oqlResults.next();
                
                if (logger.isDebugEnabled()) {
                    ICommand command = (ICommand) cmd;
                    logger.debug("Add Command with id " + command.getID() + " with state: " + 
                            command.getState() + " to list ");
                }
                results.add(cmd);
                
                // As this command has been read from the database set the persisted flag, this
                // will ensure that things that use this flag will not break
                ((Command) cmd).setPersisted(true);                
            }
            
            db.commit();
        }
        catch (Exception e) {
            logger.fatal("searchCommands error: " + e.getMessage(), e);
            
            return null;
        } finally {
            try {
                close(db);
                if (logger.isDebugEnabled()) {
                    logger.debug("End find Command trans");
                }
            } catch(Exception e) {
                logger.fatal("Close database error: " + e.getMessage());
            }
        }
        
        return(List) results;
    }

    /**
     * The method <code>retrieveTasks</code> retrieves the tasks associated with the specified command ID.
     * 
     * @param id a <code>long</code> value representing the Command id
     * 
     * @return a <code>Vector</code> with all the tasks associated with this Command id
     */
    public synchronized Vector retrieveTasks(long id) {
        if (logger.isDebugEnabled()) {
            logger.debug("In retrieveTasks.");
        }
        
        Database db = null;
        OQLQuery oql;
        QueryResults oqlResults;
        Object task = null;
        Vector results;
        StringBuffer query = new StringBuffer("SELECT c FROM org.tigr.htc.cmd.Task c where command = " + id);
        
        try {
            db = jdo.getDatabase();
            
            if (logger.isDebugEnabled()) {
                logger.debug("Searching tasks " + query);
            }
            
            db.begin();
            
            oql = db.getOQLQuery(query.toString());
            
            oqlResults = oql.execute(Database.ReadOnly);
            
            if (logger.isDebugEnabled()) {
                logger.debug("Found " + oqlResults.size());
            }
            
            results = new Vector();
            
            while(oqlResults.hasMore()) {
                task = oqlResults.next();
                
                if (logger.isDebugEnabled()) {
                    logger.debug("Add task " + task);
                }
                results.add(task);              
            }
            
            db.commit();
        }
        catch(Exception e) {
            logger.fatal("retrieveTasks error: " + e.getMessage(), e);
            
            return null;
        } finally {
            try {
                close(db);
                if (logger.isDebugEnabled()) {
                    logger.debug("End retrieveTasks Command trans");
                }
            } catch(Exception e) {
                logger.fatal("Close database error: " + e.getMessage());
            }
        }
        
        return results;
    }
    
    public synchronized Integer countCommands(String p_where) {
        
        if (logger.isDebugEnabled()) {
            logger.debug("In countCommands.");
        }
        Database db = null;
        OQLQuery oql;
        QueryResults oqlResults;
        Integer c = null;
        StringBuffer query = new StringBuffer("SELECT COUNT(*) FROM org.tigr.htc.cmd.Command c");
        
        if((p_where != null) && ( !p_where.equals(""))) {
            query.append(" WHERE " + p_where);
        }
        
        try {
            db = jdo.getDatabase();
            
            if (logger.isDebugEnabled()) {
                logger.debug("Counting commands " + query);
            }
            
            oql = db.getOQLQuery(query.toString());
            
            db.begin();
            
            oqlResults = oql.execute(Database.ReadOnly);
            
            if (logger.isDebugEnabled()) {
                logger.debug("Found " + oqlResults.size());
            }
            
            while (oqlResults.hasMore()) {
                c = (Integer) oqlResults.next();
                
                if (logger.isDebugEnabled()) {
                    logger.debug("Count is  number " + c);
                }
            }
            
            db.commit();
        }
        catch (Exception e) {
            logger.fatal("countCommands error: " + e.getMessage(), e);
            
            return null;
        } finally {
            try {
                close(db);
                if (logger.isDebugEnabled()) {
                    logger.debug("End countCommand trans");
                }
            } catch (Exception e) {
                logger.fatal("Close database error: " + e.getMessage());
            }
        }
        
        return c;
    }
    
    /* (non-Javadoc)
     * @see org.tigr.htc.persist.IPersist#delete(long, java.lang.Class)
     */
    public synchronized void delete(long pId, Class pClass) {
        
        logger.info("REMOVING " + pId);
        
        Database db = null;
        Object obj;
        
        try {
            db = jdo.getDatabase();
            
            db.begin();
            
            obj = db.load(pClass, new Long(pId));
            
            if (logger.isDebugEnabled()) {
                logger.debug("Removing object.");
            }
            db.remove(obj);
            db.commit();
        }
        catch(Exception e) {
            logger.fatal("Error: " + e.getMessage(), e);
        } finally {
            try {
                close(db);
                if (logger.isDebugEnabled()) {
                    logger.debug("End removeCommand trans");
                }
            } catch(Exception e) {
                logger.fatal("Close database error: " + e.getMessage());
            }
        }
        
    }
    
    /**
     * The method <code>close</code> closes the database connection
     * 
     * @param db a <code>Database</code> object
     * 
     * @throws Exception a <code></code>
     */
    public synchronized void close(Database db) throws Exception {
        if (db != null && !db.isClosed()) {
            db.close();
        }
    }
    
    public static void main(String [] args) {
        System.out.println("Inside main: " + System.getProperty("log4j.configuration"));
        
        PropertyConfigurator.configure(System.getProperty("log4j.configuration"));
        new CastorJDO(); // Instantiate it to just test if the configuration works
    }
}
